﻿using System;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;

namespace GDM.Hogar.Soporte.Helpers
{
    public static class Encriptador
    {
        private const int Pbkdf2IterCant = 1000;
        private const int Pbkdf2LongitudSubClave = 256 / 8; // 256 bits
        private const int TamañoSalto = 128 / 8; // 128 bits

        public static string GenerarSalto(int longByte = TamañoSalto)
        {
            return Convert.ToBase64String(GenerarSaltoInterno(longByte));
        }

        public static string Hash(string entrada, string algoritmo = "sha256")
        {
            if (entrada == null)
            {
                throw new ArgumentNullException("entrada");
            }

            return Hash(Encoding.UTF8.GetBytes(entrada), algoritmo);
        }

        public static string Hash(byte[] entrada, string algoritmo = "sha256")
        {
            if (entrada == null)
            {
                throw new ArgumentNullException("entrada");
            }

            using (HashAlgorithm alg = HashAlgorithm.Create(algoritmo))
            {
                if (alg == null)
                {
                    throw new InvalidOperationException();
                }

                byte[] datosHash = alg.ComputeHash(entrada);
                return BinarioAHexadecimal(datosHash);
            }
        }

        public static string EncriptarContraseña(string contraseña)
        {
            if (contraseña == null)
            {
                throw new ArgumentNullException("contraseña");
            }

            byte[] salto;
            byte[] subClave;
            using (var deriveBytes = new Rfc2898DeriveBytes(contraseña, TamañoSalto, Pbkdf2IterCant))
            {
                salto = deriveBytes.Salt;
                subClave = deriveBytes.GetBytes(Pbkdf2LongitudSubClave);
            }

            var bytesSalida = new byte[1 + TamañoSalto + Pbkdf2LongitudSubClave];
            Buffer.BlockCopy(salto, 0, bytesSalida, 1, TamañoSalto);
            Buffer.BlockCopy(subClave, 0, bytesSalida, 1 + TamañoSalto, Pbkdf2LongitudSubClave);
            return Convert.ToBase64String(bytesSalida);
        }

        public static string SHA1(string entrada)
        {
            return Hash(entrada, "sha1");
        }

        public static string SHA256(string entrada)
        {
            return Hash(entrada, "sha256");
        }

        public static bool VerificarContraseñaEncriptada(string contraseñaEncriptada, string contraseña)
        {
            if (contraseñaEncriptada == null)
            {
                throw new ArgumentNullException("contraseñaEncriptada");
            }
            if (contraseña == null)
            {
                throw new ArgumentNullException("contraseña");
            }

            var bytesPasswordEncriptada = Convert.FromBase64String(contraseñaEncriptada);

            if (bytesPasswordEncriptada.Length != (1 + TamañoSalto + Pbkdf2LongitudSubClave) || bytesPasswordEncriptada[0] != 0x00)
            {
                return false;
            }

            var salto = new byte[TamañoSalto];
            Buffer.BlockCopy(bytesPasswordEncriptada, 1, salto, 0, TamañoSalto);
            var subClaveGuardada = new byte[Pbkdf2LongitudSubClave];
            Buffer.BlockCopy(bytesPasswordEncriptada, 1 + TamañoSalto, subClaveGuardada, 0, Pbkdf2LongitudSubClave);

            byte[] subClaveGenerada;
            using (var deriveBytes = new Rfc2898DeriveBytes(contraseña, salto, Pbkdf2IterCant))
            {
                subClaveGenerada = deriveBytes.GetBytes(Pbkdf2LongitudSubClave);
            }
            return BytesIguales(subClaveGuardada, subClaveGenerada);
        }

        internal static string BinarioAHexadecimal(byte[] datos)
        {
            var hex = new char[datos.Length * 2];

            for (int iter = 0; iter < datos.Length; iter++)
            {
                var hexChar = ((byte)(datos[iter] >> 4));
                hex[iter * 2] = (char)(hexChar > 9 ? hexChar + 0x37 : hexChar + 0x30);
                hexChar = ((byte)(datos[iter] & 0xF));
                hex[(iter * 2) + 1] = (char)(hexChar > 9 ? hexChar + 0x37 : hexChar + 0x30);
            }
            return new string(hex);
        }

        internal static byte[] GenerarSaltoInterno(int byteLength = TamañoSalto)
        {
            var buf = new byte[byteLength];
            using (var rng = new RNGCryptoServiceProvider())
            {
                rng.GetBytes(buf);
            }
            return buf;
        }

        [MethodImpl(MethodImplOptions.NoOptimization)]
        private static bool BytesIguales(byte[] a, byte[] b)
        {
            if (ReferenceEquals(a, b))
            {
                return true;
            }

            if (a == null || b == null || a.Length != b.Length)
            {
                return false;
            }

            var sonIguales = true;
            for (int i = 0; i < a.Length; i++)
            {
                sonIguales &= (a[i] == b[i]);
            }
            return sonIguales;
        }
    }
}